-----------Crusade in Europe-----------
A 4am and san inc crack      2017-08-10
---------------------------------------

Name: Crusade in Europe
Version: 2
Genre: simulation
Year: 1985
Credits: by Sid Meier and Ed Bever,
  Apple II version by Jim Synoski
Publisher: Microprose Software
Platform: Apple ][+ or later (64K);
  double hi-res option requires an
  Apple //e or later (128K)
Media: double-sided 5.25-inch floppy
OS: Diversi-DOS C1983
Previous cracks: none (Asimov has
  uncracked .nib images)

                   ~

               Chapter 0
    In Which We're Off And Running


The disk is a standard 16-sector disk,
except track $22 which is unreadable.
Searching for "BD 8C C0" leads us to
the track $0D sector $00, from the file
named "\\". (There's another copy of
this code on track $1E sector $08, but
it's unused.)

                 --v--

0273 BD 8C C0   LDA $C08C,X
0276 BD 8E C0   LDA $C08E,X
0279 20 44 F9   JSR $F944
027C B0 10      BCS $028E
027E AD 2E 02   LDA $022E
0281 4A         LSR
0282 C5 2E      CMP $2E
0284 D0 16      BNE $029C
0286 A9 DB      LDA #$DB
0288 8D 01 02   STA $0201
028B 4C 93 02   JMP $0293
028E A9 FF      LDA #$FF
0290 8D 01 02   STA $0201
0293 BD 88 C0   LDA $C088,X
0296 AD 81 C0   LDA $C081
0299 4C A4 02   JMP $02A4
029C A9 DB      LDA #$C0
029E 8D 01 02   STA $0201
02A1 4C 93 02   JMP $0293

                 --^--

We've found the protection routine!
Looks like a soft target. The success
path at $0286 puts #$DB in $0201. Let's
patch $028F and $029D to #$DB, so $0201
ends up with the correct value even if
the protection check fails. And we're
done, right?

Wrong. While the game starts nicely, it
asks for a word from the manual before
starting any scenario. That's annoying,
and it's the second protection routine.

Let's enter the proper password and see
what happens. This is where everyone
until now made a critical mistake.
The game plays for a looong time and
everything looks fine. That is, until
it prints "Fatal error: nnn" (the
number changes each time) and hangs.

Okay, that leads to two possibilities:

  1. there's another protection check
     like the first one, or

  2. the protection check has a
     protection check of its own,
     i.e. an anti-tamper check

Actually, there's a third possibility:

  3. that both of those things are true

Let's find out.

                   ~

               Chapter 1
   In Which Our Fears Are Confirmed


The problem is that the program is
written in compiled Integer Basic, and
the result is interpreted at run-time
using a custom interpreter.

The p-code language is very simple,
composed primarily of comparisons,
transfers of control, and a couple of
arithmetic instructions and read/write
primitives.

The rest is I/O-related: fetching
keyboard input, setting various display
modes, cursor positioning, and
character printing.

It's faster than the original Basic,
and far more compact than native code,
but fast enough for the purpose.

It looks like this (and I have no idea
of the true names for the routines, I'm
just describing the behaviour):

                 --v--

.BYTE $12, $27, $00  ; jsr rel imm16
.BYTE $04, $AF, $61  ; push16 (imm16)
.BYTE $F6, $34       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $F6, $FF       ; push16 imm8
.BYTE $5C            ; push00xx (stk16)
.BYTE $C8            ; pop8 (stk16)

                 --^--

There's a dispatcher at $00AF (in zero
page), which does a load/store/jump.

Prior to that are several routines for
adjusting the instruction pointer by
popping from the stack, incrementing by
one, or adjusting according to a passed
parameter.

If we replace the store/jump with an
unconditional jump to spare memory, we
can watch the dispatcher in action. In
particular, we can see when it starts
to print the "FATAL ERROR" message, and
see who requested it. Once we find
that point, we can backtrack until we
find the start of that routine.

If we don't find the comparison that
triggers it, then we patch our
redirector to watch for someone about
to write the routine address in the
dispatcher, then use that address and
backtrack.

Lather, rinse, repeat, until we find
the comparison that sets off the whole
chain.

Time passes...

Sure enough, there's a comparison of
two 16-bit values, but not of the kind
that we expect.

Track $1F sector $0C, from the file
named "B":

                 --v--

.BYTE $04, $66, $53  ; push16 (imm16)
.BYTE $04, $84, $53  ; push16 (imm16)
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $09, $00  ; btrue rel imm16
.BYTE $12, $FF, $11  ; jsr rel imm16

                 --^--

When the two values match, the string
is printed. That looks like a timer.

Now to find where those two values are
set.

More time passes...

$5384 is incremented monotonically and
reset periodically. It looks like a
frame counter.

$5366 is much more interesting. Here,
on track $1F sector $04, also from the
file named "B":

.BYTE $04, $66, $53  ; push16 (imm16)
.BYTE $F0            ; push0
.BYTE $32            ; cmplt16 stk, stk
.BYTE $10, $04, $00  ; btrue rel imm16
.BYTE $B6            ; rts
.BYTE $F0            ; push0
.BYTE $F6, $14       ; push16 imm8
.BYTE $AC, $68, $53  ; for loop
.BYTE $04, $68, $53  ; push16 (imm16)
.BYTE $06, $B6, $52  ; push16 (imm16+
                     ;   pop16*2)
.BYTE $F0            ; push0
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $0C, $00  ; btrue rel imm16
.BYTE $F6, $48       ; push16 imm8
.BYTE $F6, $48       ; push16 imm8
.BYTE $5E            ; rand stk, stk
.BYTE $24            ; add16 stk, stk
.BYTE $08, $66, $53  ; pop16 (imm16)
.BYTE $B2, $68, $53  ; next
.BYTE $B6            ; rts

It turns out that $5366 is a flag to
indicate that a protection check
failed. It begins as $FFFF and is
checked periodically. The cmplt16
checks if $5366 is a negative
number. If so, then an array is parsed
via cmpne16 to possibly find a zero. If
one is found, then $5366 is replaced
with a RND(72)+72.

There's our timer, and we can find a
zero in that array.

But that array is scary. It means that
there's a whole set of protection
checks that might fail.

At least we know where to look. If we
instruct our dispatcher redirection to
watch for writes to those addresses,
we'll know when protection checks fail
and can backtrack to the check itself.

So, backtracking from there, and yes -
the initial protection routine has a
protection routine of its own, and yes,
it's a checksum. We've found the third
protection routine, and confirmed
possibility #2 -- the protection check
is itself protected by an anti-tamper
check.

                   ~

               Chapter 2
 In Which The Whole Is More (Or Less)
       Than The Sum Of Its Parts


The checksum code is on track $0D
sector $0B from the file named "A".
It looks like this:

                 --v--

.BYTE $AC, $E2, $52  ; for loop
.BYTE $04, $83, $54  ; push16 (imm16)
.BYTE $04, $E2, $52  ; push16 (imm16)
.BYTE $5C            ; push00xx (stk16)
.BYTE $24            ; add16 stk stk,
                     ;   pop
.BYTE $08, $83, $54  ; pop16 (imm16)
.BYTE $04, $83, $54  ; push16 (imm16)
.BYTE $02, $B8, $0B  ; push imm16
.BYTE $32            ; cmplt16 stk stk,
                     ;   pop
.BYTE $10, $0D, $00  ; btrue rel imm16
.BYTE $04, $83, $54  ; push16 (imm16)
.BYTE $02, $B6, $0B  ; push imm16
.BYTE $3E            ; sub stk, stk
.BYTE $08, $83, $54  ; pop16 (imm16)
.BYTE $B2, $E2, $52  ; next

                 --^--

It's literally a sum of the bytes in
the buffer, with a subtraction when the
value exceeds a threshold.

Using a smaller return value in the
initial protection routine (which we
did to fake success) causes this
checksum routine to behave a bit
differently.  The problem manifests
itself in this code:

                 --v--

.BYTE $F6, $2D       ; push16 imm8
.BYTE $F0            ; push0
.BYTE $0A, $B6, $52  ; pop16 (imm16+
                     ;   pop16*2)
.BYTE $04, $83, $54  ; push16 (imm16)
.BYTE $F6, $47       ; push16 imm8
.BYTE $2C            ; cmpeq16 stk stk,
                     ;   pop
.BYTE $10, $04, $00  ; bfalse rel imm16

                 --^--

The threshold isn't reached anymore on
one pass because the sum is too low, so
the resulting value is too large to
compare against a 8-bit value.

One solution is to adjust the threshold
so that the subtraction happens again.
If we lower the threshold, then the
subtraction happens again, but then a
different pass fails to trigger a
subtraction because of the distribution
of values in this buffer. If we raise
the threshold, then everything works
again.

Given a different set of values, the
opposite case could be true instead.

We raise the threshold and then we run
the game again.

The game plays for a looong time and
everything looks fine. That is, until
it prints "Fatal error: nnn" (the
number changes each time) and hangs.

Damn.

                   ~

               Chapter 3
 In Which We Are Getting Really Tired
     Of Having Our Fears Confirmed


Backtracking again, we find a new timer
setting. Track $1E sector $04, also
from the file named "B":

                 --v--

.BYTE $04, $AF, $51  ; push16 (imm16)
.BYTE $04, $68, $53  ; push16 (imm16)
.BYTE $24            ; add16 stk, stk
.BYTE $5C            ; push00xx (stk16)
.BYTE $F0            ; push0
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $0C, $00  ; btrue rel imm16
.BYTE $F6, $48       ; push16 imm8
.BYTE $F6, $48       ; push16 imm8
.BYTE $5E            ; rand stk, stk
.BYTE $24            ; add16 stk, stk
.BYTE $08, $66, $53  ; pop16 (imm16)

                 --^--

This one is also checking if a
particular memory location is zero. If
that zero is found, then $5366 is
replaced with a RND(72)+72.

A quick bit of math to get the address,
and some backtracking to find it,
reveals the routine. Track $1F sector
$04, also from the file named "B":

                 --v--

.BYTE $12, $27, $00  ; jsr rel imm16
.BYTE $04, $AF, $61  ; push16 (imm16)
.BYTE $F6, $34       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $F6, $FF       ; push16 imm8
.BYTE $5C            ; push00xx (stk16)
.BYTE $C8            ; pop8 (stk16)

                 --^--

The result of the JSR is stored to a
calculated memory location. What does
the JSR do? It calls a native function.
What does the native function do? It
checks the disk again like the first
protection routine.

We've found the fourth protection
routine, and confirmed possibility #1
(and by extension, #3).

The code for the fourth protection
routine looks like this:

                 --v--

$E251 AD 14 E1   LDA $E114
$E254 20 98 E3   JSR $E398
$E257 AD 14 E1   LDA $E114
$E25A 20 AA E2   JSR $E2AA
$E25D 20 8D E2   JSR $E28D
$E260 90 0B      BCC $E26D
$E262 CE F6 E2   DEC $E2F6
$E265 30 0A      BMI $E271
$E267 20 35 E3   JSR $E335
$E26A 4C 51 E2   JMP $E251
$E26D A9 00      LDA #$00
$E26F F0 02      BEQ $E273
$E271 A9 FF      LDA #$FF
$E273 48         PHA
...

It checks in a loop for the special
track, then returns success or failure.

Seems like a simple change. There's
just one thing wrong, though: searching
the disk doesn't find that code.

The reason is that it's all byte-
swapped. And relocated.

Track $0B sector $0D, from the file
named "SHR1":

                 --v--

; #$61 becomes #$E2
$E26B 4C A9 61   JMP $61A9
$E26E F0 00      BEQ $E270
$E270 A9 02      LDA #$02
$E272 48         PHA
$E273 FF         ???

                 --^--

Eeew. Still, replacing that #$FF should
fix it.

More time passes... It is getting dark.
You are likely to be eaten by an anti-
tamper grue.

The game plays for a looong time and
everything looks fine. That is, until
it prints "Fatal error: nnn" (the
number changes each time) and hangs.

Sigh.

Backtracking again, we find that the
array has a different entry zeroed out.

We've found the fifth protection
routine.

                   ~

               Chapter 4
     In Which Some Words Are Hard
       But Word Sums Are Harder


But here's a new thing: the game plays
for a looong time and everything looks
fine. Really fine. It doesn't hang
anymore. So we're done. Celebrate!

Well, no. There's that pesky manual
protection that needs to go away.

We could cheat and make any answer
work. Yes, that's one way to do it, and
it's what I wanted to do to be done
with it. But 4am said "no," so no. If
you type nothing at the codeword lookup
screen, the game enters demonstration
mode. We want this mode to remain
available, so the "type anything"
option won't work.

We choose to take a different path. The
first thing is to reverse the logic so
that typing nothing would enter the
game proper, and typing the proper word
would enter demonstration mode.

I fix that.

Except that the "fatal error" message
came back. Yes, the p-code itself has a
checksum. We've found the sixth
protection routine.

Another point of interest regarding the
manual check is that if you type the
wrong word, the game prints "You are an
enemy spy" and then enters demo mode.

I found the check that causes the
"enemy spy" text to be printed. I
change it to print the "demonstration
mode" text instead.

The "fatal error" message came back.

We've found the seventh protection
routine. Yes, that part of the p-code
has a separate checksum.

I fix the checksum for that.

Great, but the message still asks for a
word from the manual. We want to fix
that.

I spent some hours crafting the perfect
wording for the prompt. It was crap and
we threw it out.

4am spent some minutes crafting some
wording for the prompt. It was perfect.
That's a fine skill.

I put the text in. Of course, the
"fatal error" message came back. Yes,
that part of the p-code also has a
*separate* checksum.

We've found the eighth and final
protection routine...

of side A.

                   ~

               Chapter 5
       In Which We Flip The Disk
 And Immediately Regret This Decision


So far, this 128K version has had
similar protection to the 64K version
(crack no. 1353). But this version has
an option to use double hi-res graphics
throughout the game, and those files
are on side B.

The second side protection is just like
the first side, except that it isn't.

Side B contains all the same protection
check as side A, but even after we
defeat those, the "fatal error" message
still appears.

When searching for the same checksum
values to change, we run into a funny
coincidence.

                 --v--

      4C CB 9F   JMP $9FCB
      4C 93 B7   JMP $B793
      4C F6 82   JMP $82F6
      4C B7 0B   JMP $0BB7
      4C C1 0C   JMP $0CC1

                 --^--

On first glance, this looks like an
ordinary looking jump table, including
the well-known address $B793 (standard
high-level entry point for reading
multiple sectors from disk). But the
jump table also happens to have some of
the same values as the checksum we're
looking for. Coincidence?

We might be suspicious that the game's
DOS is in the language card, so $B793
doesn't point to anything meaningful.

Oh, and that jump table? According to a
track/sector map (thanks Copy II Plus),
it's not part of any file. It's in an
unallocated sector on the disk (track
$0E sector $04).

We're just full of coincidences today.

Backtracking, we find this p-code:

                 --v--

.BYTE $04, $A1, $61  ; push16 (imm16)
.BYTE $04, $2B, $62  ; push16 (imm16)
.BYTE $F6, $08       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $5C            ; push00xx (stk16)
.BYTE $C8            ; pop8 (stk16)
.BYTE $04, $9F, $61  ; push16 (imm16)
.BYTE $04, $2B, $62  ; push16 (imm16)
.BYTE $F6, $07       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $5C            ; push00xx (stk16)
.BYTE $C8            ; pop8 (stk16)
.BYTE $04, $00, $61  ; push16 (imm16)
.BYTE $04, $84, $63  ; push16 (imm16)
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $0E, $09, $00  ; bfalse rel imm16

                 --^--

And the value at $622B points to the
start of the jump table in memory *that
someone loaded*.

And the add of #$08 in the first case,
and #$07 in the second case, points to
the $82F6, which is then fetched and
stored.

And that $82F6 happens to be one of the
checksums of interest.

There are no coincidences. We've found
the ninth protection routine.

Another p-code routine performs the
same operations after adding #$05 and
#$04 to fetch the $B793 instead, which
makes it the... [counts furiously]
TENTH PROTECTION ROUTINE.

We patch both copies of the checksums,
and, at long last, the game runs
properly. And there was much rejoicing.

Quod erat liberandum.

                   ~

            Acknowledgments


Thanks to 4am for editing and reviewing
drafts of this write-up.

---------------------------------------
docs by qkumba                 No. 1358
------------------EOF------------------
